package com.netflexitysolutions.amazonws.sdb.orm.internal.operations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.netflexitysolutions.amazonws.sdb.Item;
import com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSession;
import com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionListener;
import com.netflexitysolutions.amazonws.sdb.orm.filter.Predicate;
import com.netflexitysolutions.amazonws.sdb.orm.internal.util.GenericRecordComparator;
import com.netflexitysolutions.amazonws.sdb.orm.internal.util.QueryUtils;
import com.netflexitysolutions.amazonws.sdb.orm.internal.util.StringConstants;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ItemMetadata;

public class Select<T> implements Callable<List<T>> {

	private static Log logger = LogFactory.getLog(Select.class.getName());
	
	private SimpleDBSession session;
	private Predicate<T> predicate;
	private String[] orderBy;
	private T oneToManyParent;
	
	/**
	 * @param session
	 * @param predicate
	 */
	public Select(SimpleDBSession session, Predicate<T> predicate) {
		this(session, predicate, new String[]{});
	}
	
	/**
	 * @param session
	 * @param predicate
	 * @param orderBy
	 */
	public Select(SimpleDBSession session, Predicate<T> predicate, String ... orderBy) {
		this.session = session;
		this.predicate = predicate;
		this.orderBy = orderBy;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.concurrent.Callable#call()
	 */
	public List<T> call() throws Exception {
		// Get query.
		String query = getQuery();
        
        // Add order by clause.
        // Note: can only sort by items present in SELECT list. (*) will not work.
        /*if(orderBy != null && orderBy.length > 0){
        	selectQuery.append(QueryUtils.ORDER_BY);
        	for (int i = 0; i < orderBy.length; i++) {
        		selectQuery.append(orderBy[i]);
        		if(i < (orderBy.length -1)){
        			selectQuery.append(StringConstants.COMMA);
        		}
			}
        }*/
        
        logger.debug("About to execute select query:" + query);
        
        // Obtain metadata for predicate record type.
        ItemMetadata metadata = session.getMetadata(predicate.getType());
        assert(metadata != null);
        
		// Invoke before select.
		List<SimpleDBSessionListener> listeners = session.getFactory().getEntityInterceptors();
		for (SimpleDBSessionListener simpleDBSessionListener : listeners) {
			simpleDBSessionListener.beforeSelect(metadata);
		}
		
        // Create appropriate Amazon SDB mapper.
        SimpleDBItemMapper<T> mapper = new SimpleDBItemMapper<T>(session, metadata);
        mapper.setOneToManyParent(oneToManyParent);
        
        // Iterate through results and convert items to record objects.
        List<T> records = new ArrayList<T>(100);
        Iterator<List<Item>> itemIterator = session.getFactory().getDataSource().select(query);
        while (itemIterator.hasNext()) {
			List<Item> list = (List<Item>) itemIterator.next();
			for (Item item : list) {
				records.add(mapper.mapItem(item));
			}
		}
        
        // Retrieve all records in parallel.
        /*ConcurrentFinder<T> finder = new ConcurrentFinder<T>(session, clazz, ids);
        List<T> records = finder.find();
        if(logger.isDebugEnabled()){
        	logger.debug("Found (" + records.size() + ") for domain " + domain.getName());
        }*/
        
        // Apply orderBy.
        if(orderBy != null && orderBy.length > 0){
        	Collections.sort(records, new GenericRecordComparator<T>(session.getMetadata(predicate.getType()), orderBy));
        }
        
        return records;
	}

	/**
	 * @return
	 */
	public String getQuery(){
		// Get item metadata.
		ItemMetadata itemMetadata = session.getMetadata(predicate.getType());
		
		// Get where clause.
		String where = predicate.toSelect();
		
        // Construct query.
        StringBuffer selectQuery = new StringBuffer(1024);
        selectQuery.append(QueryUtils.SELECT_ALL_FROM).append(StringConstants.TICK).append(itemMetadata.getDomainName()).append(StringConstants.TICK);
        if(StringUtils.isNotBlank(where)){
        	selectQuery.append(QueryUtils.WHERE).append(where);
        }
        
        // Handle logical name.
        if(StringUtils.isNotBlank(itemMetadata.getLogicalName())){
        	if(StringUtils.isBlank(where)){
        		selectQuery.append(QueryUtils.WHERE);
        	}
        	else{
        		selectQuery.append(QueryUtils.AND);
        	}
        	selectQuery.append(StringConstants.TICK).append(itemMetadata.getLogicalNameColumnName()).append(StringConstants.TICK)
        	.append(StringConstants.EQ)
        	.append(StringConstants.DOUBLE_QUOTE).append(itemMetadata.getLogicalName()).append(StringConstants.DOUBLE_QUOTE);
        }
        
        return selectQuery.toString();
	}
	
	/**
	 * @return the session
	 */
	public SimpleDBSession getSession() {
		return session;
	}

	/**
	 * @param session the session to set
	 */
	public void setSession(SimpleDBSession session) {
		this.session = session;
	}
	
	/**
	 * @param orderBy the orderBy to set
	 */
	public void setOrderBy(String ... orderBy) {
		this.orderBy = orderBy;
	}

	/**
	 * @return the oneToManyParent
	 */
	public T getOneToManyParent() {
		return oneToManyParent;
	}

	/**
	 * @param oneToManyParent the oneToManyParent to set
	 */
	public void setOneToManyParent(T oneToManyParent) {
		this.oneToManyParent = oneToManyParent;
	}
}
